package dnl.toolbox.lang;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

public class ClassPathHacker {
	private static final Class<?>[] parameters = new Class[] { URL.class };

	public static void addFile(String s) throws IOException {
		File f = new File(s);
		addFile(f);
	}

	public static void addFile(String s, String identifyingResource) throws IOException {
		File f = new File(s);
		addFile(f, identifyingResource);
	}

	public static void addFile(File f) throws IOException {
		addURL(f.toURI().toURL());
	}

	public static void addFile(File f, String identifyingResource) throws IOException {
		addURL(f.toURI().toURL(), identifyingResource);
	}

	public static void addURL(URL u) throws IOException {
		URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
		addUrlImpl(u, sysloader);
	}

	public static void addURL(URL u, String identifyingResource) throws IOException {
		ClassLoader classLoader = ClassPathHacker.class.getClassLoader();
		while (classLoader != null) {
			if (addUrlToClassLoader(u, classLoader, identifyingResource)) {
				return;
			}
			classLoader = classLoader.getParent();
		}

	}

	/**
	 * 
	 * @param u
	 * @param cloader
	 * @param identifyingResource
	 *            used in order to locate a specific ClassLoader. if this class
	 *            is not found by a candidate ClassLoader, it will be left
	 *            unchanged.
	 * @throws IOException
	 */
	private static boolean addUrlToClassLoader(URL u, ClassLoader cloader, String identifyingResource)
			throws IOException {
		if (!URLClassLoader.class.isAssignableFrom(cloader.getClass())) {
			return false;
		}

		if (identifyingResource != null) {
			URL fromSuvModel = cloader.getResource(identifyingResource);
			if (fromSuvModel == null) {
				return false;
			}
		}

		// System.out.println("addUrl to cloader>> " + u.toString());
		return addUrlImpl(u, cloader);
	}

	private static boolean addUrlImpl(URL u, ClassLoader cloader) throws IOException {
		Class<?> sysclass = URLClassLoader.class;

		try {
			Method method = sysclass.getDeclaredMethod("addURL", parameters);
			method.setAccessible(true);
			method.invoke(cloader, new Object[] { u });
			return true;
		} catch (Throwable t) {
			t.printStackTrace();
			throw new IOException("Error, could not add URL to system classloader");
		}
	}

	/**
	 * Adds the value of any property that starts with 'class.path.entry' to the
	 * classpath.
	 * 
	 * @param configFile
	 */
	public static void load(Reader reader) {
		Properties properties = new Properties();
		try {
			properties.load(reader);
		} catch (IOException e) {
			e.printStackTrace();
		}
		load(properties);
	}

	/**
	 * Adds the value of any property that starts with 'class.path.entry' to the
	 * classpath.
	 * 
	 * @param properties
	 */
	public static void load(Properties properties) {
		String identifyingResource = (String) properties.get("class.path.identifying.resource");
		Set<Entry<Object, Object>> entrySet = properties.entrySet();
		for (Entry<Object, Object> entry : entrySet) {
			String key = (String) entry.getKey();
			String val = (String) entry.getValue();
			if (key.startsWith("class.path.entry")) {
				try {
					String replaceShellVars = ShellEnvironment.replaceShellVariables(val);
					ClassPathHacker.addFile(replaceShellVars, identifyingResource);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
